﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security;
using Pfz.Extensions;
using Pfz.RemoteGaming.Internal;
using Pfz.Threading;

namespace Pfz.RemoteGaming
{
	/// <summary>
	/// Class that must be inherited to make a game component.
	/// Note that properties that are send from client to server and vice-versa must be declared as
	/// abstract get/set. The remote game engine will emit the appropriate classes to deal with
	/// get/set changes and to notify the other side of the changed values.
	/// </summary>
	[Serializable]
	public abstract class RemoteGameComponent:
		IAdvancedDisposable,
		ISerializable
	{
		#region Fields
			internal long _id;
			internal RemoteGameClient _client;
			internal Dictionary<int, object> _values = new Dictionary<int, object>();
			internal Dictionary<int, object> _changes = new Dictionary<int, object>();

			internal object _roomLock;
			internal YieldReaderWriterLockSlim _componentLock;
			internal bool _wasDisposed;
		#endregion

		#region Destructor
			/// <summary>
			/// Finalizer. Calls Dispose(false);
			/// </summary>
			~RemoteGameComponent()
			{
				Dispose(false);
			}
		#endregion
		#region Dispose
			#region Dispose()
				/// <summary>
				/// Frees all managed and unmanaged resources. Calls Dispose(true);
				/// </summary>
				public void Dispose()
				{
					Dispose(true);
					GC.SuppressFinalize(this);
				}
			#endregion
			#region Dispose(bool disposing)
				/// <summary>
				/// Releases all resources used by this component.
				/// </summary>
				protected virtual void Dispose(bool disposing)
				{
					if (disposing)
					{
						_componentLock.EnterWriteLock();
						try
						{
							if (_wasDisposed)
								return;

							_wasDisposed = true;
						}
						finally
						{
							_componentLock.ExitWriteLock();
						}

						var room = _room;
						if (room != null)
						{
							_room = null;

							if (IsPublic)
								room._RemoveComponent(this);
						}

						var owner = _owner;
						if (owner != null)
						{
							_owner = null;
							if (!IsPublic)
								owner._RemoveComponent(this);
						}

						_values = null;
						_changes = null;
						_client = null;
					}
				}
			#endregion
		#endregion

		#region Properties
			#region WasDisposed
				/// <summary>
				/// Gets a value indicating if this object was disposed.
				/// </summary>
				public bool WasDisposed
				{
					get
					{
						return _wasDisposed;
					}
				}
			#endregion

			#region IsClientComponent
				/// <summary>
				/// This is more useful to the framework itself.
				/// It tells if the component was created by a RemoteGameClient (true) or by a RemoteGameRoom/Participant (false).
				/// </summary>
				public bool IsClientComponent
				{
					get
					{
						return _client != null;
					}
				}
			#endregion
			#region IsPublic
				/// <summary>
				/// On the server side, gets a value indicating if this component is public (seen by all participants)
				/// or not. For the client, this property is always false.
				/// </summary>
				public bool IsPublic { get; internal set; }
			#endregion
			#region Room
				internal RemoteGameRoom _room;
				/// <summary>
				/// Gets the room in which this component was created.
				/// </summary>
				public RemoteGameRoom Room
				{
					get
					{
						return _room;
					}
				}

			#endregion
			#region Owner
				internal RemoteGameParticipant _owner;
				/// <summary>
				/// Gets the owner of this component.
				/// </summary>
				public RemoteGameParticipant Owner
				{
					get
					{
						return _owner;
					}
					internal set
					{
						_owner = value;
						OwnerId = value.ParticipantId;
					}
				}
			#endregion
			#region OwnerId
				/// <summary>
				/// Gets the Id of the client that created this component.
				/// Do not implement it, as it needs to be abstract by the engine.
				/// </summary>
				public abstract long OwnerId { get; set; }
			#endregion
		#endregion
		#region Methods
			#region _GetChanges
				internal KeyValuePair<int, object>[] _GetChanges()
				{
					bool wasUpgraded = false;
					_componentLock.EnterUpgradeableLock();
					try
					{
						if (_wasDisposed)
							return null;

						int count = _changes.Count;
						if (count == 0)
							return null;

						var result = new KeyValuePair<int, object>[count];
						int index = -1;
						foreach(var pair in _changes)
						{
							index++;
							result[index] = pair;
						}

						wasUpgraded = true;
						_componentLock.UpgradeToWriteLock();
						_changes.Clear();
						return result;
					}
					finally
					{
						if (wasUpgraded)
							_componentLock.ExitUpgradedLock();
						else
							_componentLock.ExitUpgradeableLock();
					}
				}
			#endregion
			#region _AddVolatileValues
				internal void _AddVolatileValues(List<KeyValuePair<long, object[]>> valuePairs)
				{
					var volatileProperties = RemoteGameProperty._GetVolatileProperties(GetType());
					int count = volatileProperties.Length;
					if (count == 0)
						return;

					object[] values = new object[count];
					_componentLock.EnterReadLock();
					try
					{
						if (WasDisposed)
							return;

						for(int i=0; i<count; i++)
						{
							var volatileProperty = volatileProperties[i];
							values[i] = _values.GetValueOrDefault(volatileProperty._id);
						}
					}
					finally
					{
						_componentLock.ExitReadLock();
					}

					valuePairs.Add(new KeyValuePair<long,object[]>(_id, values));
				}
			#endregion
			#region _ApplyVolatileValues
				internal void _ApplyVolatileValues(object[] values, RemoteGamePropertyChanges changes)
				{
					var volatileProperties = RemoteGameProperty._GetVolatileProperties(GetType());
					int count = volatileProperties.Length;
					if (count != values.Length)
						throw new RemoteGameException("Wrong number of volatile values.");
			
					_componentLock.EnterWriteLock();
					try
					{
						for(int i=0; i<count; i++)
						{
							var volatileProperty = volatileProperties[i];
							object value = values[i];

							var oldValue = _values.GetValueOrDefault(volatileProperty._id);

							if (object.Equals(oldValue, value))
								continue;

							if (value == null)
								_values.Remove(volatileProperty._id);
							else
								_values[volatileProperty._id] = values[i];

							var change = new RemoteGamePropertyChange(oldValue, value);
							changes._Set(volatileProperty, change);
						}
					}
					finally
					{
						_componentLock.ExitWriteLock();
					}
				}
			#endregion

			#region OnInitialize
				/// <summary>
				/// Implement this to initialize the object. Trying to use the game properties from the constructor doesn't work.
				/// In the client, the OnInitialize only happens after receiving all initial values from the server.
				/// </summary>
				internal protected virtual void OnInitialize()
				{
				}
			#endregion
			#region OnChanged
				/// <summary>
				/// Method invoked when a RemoteGameClient applies changes to this component based on
				/// server changes. All changes in a given frame are notified together.
				/// </summary>
				internal protected virtual void OnChanged(RemoteGamePropertyChanges changes)
				{
				}
			#endregion

			#region CheckUndisposed
				/// <summary>
				/// Throws an exception if this component was disposed.
				/// </summary>
				internal protected void CheckUndisposed()
				{
					if (_wasDisposed)
						throw new RemoteGameObjectDisposedException(GetType().FullName);
				}
			#endregion
			#region OnParticipantDisconnected
				internal bool _isParticipantDisconnected;
				/// <summary>
				/// Method invoked when the Participant that owns this control disconnects.
				/// It has no effect on client side.
				/// </summary>
				internal protected virtual void OnParticipantDisconnected()
				{
					_isParticipantDisconnected = true;
				}
			#endregion
		#endregion

		#region ISerializable
			[SecurityCritical]
			void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
			{
				info.AssemblyName = typeof(_RemoteGameComponentReference).Assembly.FullName;
				info.FullTypeName = typeof(_RemoteGameComponentReference).FullName;

				info.AddValue("_ownerId", OwnerId);
				info.AddValue("_id", _id);
			}
		#endregion

	}
}
